/*
 * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: CC0-1.0
 */

#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"

#include "nvs.h"
#include "nvs_flash.h"
#include "esp_system.h"
#include "esp_netif.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_system.h"
#include "esp_sleep.h"

#include "lwip/err.h"
#include "lwip/sys.h"

#include "driver/rmt_types.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_encoder.h"

#include "mqtt_client.h"
#include "esp_smartconfig.h"
#include "driver/gpio.h"

static const char *TAG = "My RMT";
static int s_retry_num = 0;

static EventGroupHandle_t s_wifi_event_group;
static const int NETWORK_CONFIGED_BIT = BIT0;
static const int ESPTOUCH_DONE_BIT = BIT1;

/**
 * 标记是否曾经成功配置过网络，
 * 其保存在nvs中
 */
#define NVS_NAMESPACE "STORGE"
#define NETWORK_STATE_NVS_KEY "NETWORK_STATE"
enum NETWORK_STATE_IN_NVS
{
    NETWORK_NOT_CONFIGED,
    NETWORK_CONFIGED,
};

// GPIO相关设置
#define GPIO_OUTPUT_0 CONFIG_GPIO_OUTPUT_0
#define GPIO_OUTPUT_PIN_SEL (1ULL << GPIO_OUTPUT_0)
#define GPIO_INPUT_IO_0 GPIO_NUM_9
#define GPIO_INPUT_PIN_SEL 1ULL << GPIO_INPUT_IO_0
#define ESP_INTR_FLAG_DEFAULT 0

// MQTT
#define MQTT_URI "mqtt://192.168.50.229:1883"
#define MQTT_SUB_TOPIC "home/bedroom/switch/command"
#define MQTT_PUB_TOPIC "home/bedroom/switch/attribute"
const char *SWITCH_ON = "ON";
const char *SWITCH_OFF = "OFF";

static void log_error_if_nonzero(const char *message, int error_code)
{
    if (error_code != 0)
    {
        ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
    }
}

/**
 * MQTT事件回调函数
*/
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%" PRIi32 "", base, event_id);
    esp_mqtt_event_handle_t event = event_data;
    esp_mqtt_client_handle_t client = event->client;
    int msg_id;
    switch ((esp_mqtt_event_id_t)event_id)
    {
    case MQTT_EVENT_CONNECTED:
        msg_id = esp_mqtt_client_subscribe(client, MQTT_SUB_TOPIC, 1);
        ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
        break;
    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
        break;

    case MQTT_EVENT_SUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_UNSUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_PUBLISHED:
        ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_DATA:
        ESP_LOGI(TAG, "MQTT_EVENT_DATA");
        printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
        printf("DATA=%.*s\r\n", event->data_len, event->data);

        if (strncmp(SWITCH_ON, event->data, strlen(SWITCH_ON)) == 0)
        {
            ESP_LOGI(TAG, "SET SWITCH ON");
            ESP_ERROR_CHECK(gpio_set_level(GPIO_OUTPUT_0, 1));
        }

        if (strncmp(SWITCH_OFF, event->data, strlen(SWITCH_OFF)) == 0)
        {
            ESP_LOGI(TAG, "SET SWITCH OFF");
            ESP_ERROR_CHECK(gpio_set_level(GPIO_OUTPUT_0, 0));
        }

        int level = gpio_get_level(GPIO_OUTPUT_0);
        printf("level:%d\r\n", level);

        if (level == 0)
        {
            esp_mqtt_client_publish(client, MQTT_PUB_TOPIC, SWITCH_OFF, strlen(SWITCH_OFF), 0, 0);
        }
        else
        {
            esp_mqtt_client_publish(client, MQTT_PUB_TOPIC, SWITCH_ON, strlen(SWITCH_ON), 0, 0);
        }
        break;
    case MQTT_EVENT_ERROR:
        ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
        if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT)
        {
            log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
            log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
            log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno);
            ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
        }
        break;
    default:
        ESP_LOGI(TAG, "Other event id:%d", event->event_id);
        break;
    }
}

/**
 * 连接MQTT服务器
*/
static void mqtt_app_start(void)
{
    esp_mqtt_client_config_t mqtt_cfg = {
        .broker.address.uri = MQTT_URI,
    };
    esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
    ESP_ERROR_CHECK(esp_mqtt_client_start(client));
}

/**
 * @brief SmartConfig事件的回调函数，用于配置网络，并将配置信息保存到nvs中
*/
static void smartconfig_task(void *parm)
{
    EventBits_t uxBits;
    ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH));
    smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_smartconfig_start(&cfg));
    while (1)
    {
        uxBits = xEventGroupWaitBits(s_wifi_event_group, ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY);
        if (uxBits & ESPTOUCH_DONE_BIT)
        {
            esp_smartconfig_stop();
            vTaskDelete(NULL);
        }
    }
}

/**
 * @brief WiFi事件的回调函数
*/
static void event_handler(void *event_handler_arg,
                          esp_event_base_t event_base,
                          int32_t event_id,
                          void *event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
    {
        EventBits_t uxBits = xEventGroupGetBits(s_wifi_event_group);
        if (uxBits & NETWORK_CONFIGED_BIT)
        {
            ESP_ERROR_CHECK(esp_wifi_connect());
        }
        else
        {
            xTaskCreate(smartconfig_task, "smartconfig_task", 4096, NULL, 3, NULL);
        }
    }
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
    {
        if (s_retry_num < 10)
        {
            ESP_ERROR_CHECK(esp_wifi_connect());
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        }
        else
        {
            ESP_LOGI(TAG, "connect to the AP fail");
            // ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(60*1000*1000));
            esp_deep_sleep(60 * 1000 * 1000);
            s_retry_num = 0;
            ESP_ERROR_CHECK(esp_wifi_connect());
        }
    }
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
    {
        ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;

        mqtt_app_start();
    }
    else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD)
    {
        smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data;
        wifi_config_t wifi_config;
        bzero(&wifi_config, sizeof(wifi_config_t));
        memcpy(wifi_config.sta.ssid, evt->ssid, sizeof(wifi_config.sta.ssid));
        memcpy(wifi_config.sta.password, evt->password, sizeof(wifi_config.sta.password));
        wifi_config.sta.bssid_set = evt->bssid_set;
        if (wifi_config.sta.bssid_set == true)
        {
            memcpy(wifi_config.sta.bssid, evt->bssid, sizeof(wifi_config.sta.bssid));
        }
        nvs_handle_t nvs_handle;
        ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle));
        ESP_ERROR_CHECK(nvs_set_u8(nvs_handle, NETWORK_STATE_NVS_KEY, 1));
        nvs_close(nvs_handle);
        xEventGroupSetBits(s_wifi_event_group, NETWORK_CONFIGED_BIT);
        ESP_ERROR_CHECK(esp_wifi_disconnect());
        ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
        esp_wifi_connect();
    }
}

void init_wifi() {
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
    assert(sta_netif);
    wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config));

    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT,
                                               ESP_EVENT_ANY_ID,
                                               &event_handler,
                                               NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT,
                                               ESP_EVENT_ANY_ID,
                                               &event_handler,
                                               NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(SC_EVENT,
                                               ESP_EVENT_ANY_ID,
                                               &event_handler,
                                               NULL));

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    EventBits_t uxBits = xEventGroupGetBits(s_wifi_event_group);
    if (uxBits & NETWORK_CONFIGED_BIT)
    {
        wifi_config_t wifi_config;
        ESP_ERROR_CHECK(esp_wifi_get_config(WIFI_IF_STA, &wifi_config));
    }
    ESP_ERROR_CHECK(esp_wifi_start());

}
/**
 * @brief 检查nvs中是否有网络配置信息。
 * 如果GPIO_NUM_6接地启动，则清除网络配置，可重新配网
*/
void check_network_configured()
{
    s_wifi_event_group = xEventGroupCreate();
    nvs_handle_t nvs_handle;
    ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle));

    // 设置清除网络参数功能，如果GPIO_NUM_6接地启动，则清除
    ESP_ERROR_CHECK(gpio_set_direction(GPIO_NUM_6, GPIO_MODE_INPUT));
    ESP_ERROR_CHECK(gpio_pullup_en(GPIO_NUM_6));
    int level = gpio_get_level(GPIO_NUM_6);
    if (level == 0)
    {
        nvs_set_u8(nvs_handle, NETWORK_STATE_NVS_KEY, NETWORK_NOT_CONFIGED);
        xEventGroupClearBits(s_wifi_event_group, NETWORK_CONFIGED_BIT);
    }

    uint8_t flag = 0;
    esp_err_t ret = nvs_get_u8(nvs_handle, NETWORK_STATE_NVS_KEY, &flag);

    if (ret == ESP_OK)
    {
        if (flag == NETWORK_CONFIGED)
        {
            xEventGroupSetBits(s_wifi_event_group, NETWORK_CONFIGED_BIT);
        }
    }
    else
    {
        xEventGroupClearBits(s_wifi_event_group, NETWORK_CONFIGED_BIT);
    }
    nvs_close(nvs_handle);
}

/*
 * @brief 设置控制继电器的输出IO端口
 */
void init_gpio()
{
    // zero-initialize the config structure.
    gpio_config_t io_output_conf = {};
    memset(&io_output_conf, 0, sizeof(gpio_config_t));
    // disable interrupt
    io_output_conf.intr_type = GPIO_INTR_DISABLE;
    // 必须设置为INPUT_OUTPUT模式，否则无法读取其电平状态
    io_output_conf.mode = GPIO_MODE_INPUT_OUTPUT;
    // bit mask of the pins that you want to set,e.g.GPIO18/19
    io_output_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
    // disable pull-down mode
    io_output_conf.pull_down_en = 0;
    // disable pull-up mode
    io_output_conf.pull_up_en = 0;
    // configure GPIO with the given settings
    ESP_ERROR_CHECK(gpio_config(&io_output_conf));
}

/*
 * @brief 初始化NVS库(non-Volatile Storage Library).
 *
 * @details NVS Library用于在flash中，存储key-value格式的数据。
 * 其中key为ASCII字符串，最长为15个字符。
 * value可以是整数型，字符串，BLOB(Binary Large Object)
 */
void init_nvs()
{
    // Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);
}

void app_main(void)
{
    init_nvs();
    init_gpio();

    check_network_configured();

    init_wifi();
}